/*********************************************************
          File Name:aio_test.c
          Author: Abby Cin
          Mail: abbytsing@gmail.com
          Created Time: Sun 30 Aug 2015 11:40:21 AM CST
**********************************************************/

#include <stdio.h>
#include <errno.h>
#include <aio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

// link with -lrt

#define N       4

enum Status
{
        fresh,
        reading,
        writing
};

struct me 
{
        enum Status status;
        int done;
        struct aiocb aio;
        //unsigned char *data;    // cause error: Invalid argument
        unsigned char data[4096];       // assume blocksize = 4096
};

void aio_cp(int fdin, int fdout)
{
        struct stat sbuf;
        memset(&sbuf, 0, sizeof(sbuf));

        fstat(fdin, &sbuf);
        size_t size = sbuf.st_size;
        size_t offset = 0, tmp, indicator;
        int blk = sbuf.st_blksize;
        struct me buf[N];
        const struct aiocb *list[N];   // aio_suspend(const struct aiocbi* const ,...)
        int i, error;
        ssize_t n;

        for(i = 0; i < N; i++)
        {
                buf[i].status = fresh;
                //buf[i].data = (unsigned char *)alloca(blk);
                buf[i].aio.aio_buf = buf[i].data;
                buf[i].aio.aio_sigevent.sigev_notify = SIGEV_NONE;
                list[i] = NULL;
        }

        indicator = 0;

        for(;;)
        {
                for(i = 0; i < N; i++)
                {
                        switch(buf[i].status)
                        {
                                case fresh:
                                        if(offset < size)
                                        {
                                                buf[i].status = reading;
                                                buf[i].aio.aio_fildes = fdin;
                                                buf[i].aio.aio_nbytes = blk;
                                                buf[i].aio.aio_offset = offset;
                                                offset += blk;
                                                
                                                if(offset >= size)
                                                        buf[i].done = 1;
                                                if(aio_read(&buf[i].aio) < 0)
                                                        fprintf(stderr, "LINE:%d, %s\n", __LINE__, strerror(errno));
                                                list[i] = &buf[i].aio;
                                                indicator += 1;
                                        }
                                        break;
                                case reading:
                                        if((error = aio_error(&buf[i].aio)) == EINPROGRESS)
                                                continue;
                                        if(error != 0)
                                        {
                                                if(error == -1)
                                                        fprintf(stderr, "LINE:%d, %s\n", __LINE__, strerror(errno));
                                                else
                                                {
                                                        fprintf(stderr, "LINE:%d, %s\n", __LINE__, strerror(errno));
                                                        exit(1);
                                                }
                                        }

                                        // read done
                                        
                                        if((n = aio_return(&buf[i].aio)) < 0)
                                                fprintf(stderr, "LINE:%d, %s\n", __LINE__, strerror(errno));
                                        if(n != blk && buf[i].done != 1)
                                        {
                                                fprintf(stderr, "read \e[35m%ld/%d\e[0m\n", n, blk);
                                                exit(1);
                                        }

                                        // no error, change status
                                        buf[i].status = writing;
                                        buf[i].aio.aio_fildes = fdout;
                                        buf[i].aio.aio_nbytes = n;
                                        
                                        if(aio_write(&buf[i].aio) < 0)
                                                fprintf(stderr, "LINE:%d, %s\n", __LINE__, strerror(errno));
                                        break;
                                case writing:
                                        if((error = aio_error(&buf[i].aio) == EINPROGRESS))
                                                continue;
                                        if(error != 0)
                                        {
                                                if(error == -1)
                                                        fprintf(stderr, "LINE:%d, %s\n", __LINE__, strerror(errno));
                                                else
                                                {
                                                        fprintf(stderr, "LINE:%d, %s\n", __LINE__, strerror(errno));
                                                        exit(1);
                                                }
                                        }

                                        // write done
                                        
                                        if((n = aio_return(&buf[i].aio)) < 0)
                                                fprintf(stderr, "LINE:%d, %s\n", __LINE__, strerror(errno));
                                        tmp = n;
                                        if(tmp != buf[i].aio.aio_nbytes)
                                        {
                                                fprintf(stderr, "write \e[35m%ld/%d\e[0m\n", tmp, blk);
                                                exit(1);
                                        }

                                        list[i] = NULL;
                                        buf[i].status = fresh;  // recycle
                                        indicator -= 1;
                                        break;
                        }
                }
                if(indicator == 0)
                {
                        if(offset >= size)
                                break;          // EOF
                }
                else
                {
                        if(aio_suspend(list, N, NULL) < 0)
                                fprintf(stderr, "LINE:%d, %s\n", __LINE__, strerror(errno));
                }
        }

        // optional
        buf[0].aio.aio_fildes = fdout;
        if(aio_fsync(O_SYNC, &buf[i].aio) < 0)
                fprintf(stderr, "LINE:%d, %s\n", __LINE__, strerror(errno));
}

int main(int argc, char *argv[])
{
        int fdin, fdout;

        if(argc != 3)
        {
                fprintf(stderr, "%s srcfile dstfile\n", argv[0]);
                exit(1);
        }

        fdin = open(argv[1], O_RDONLY);
        fdout = open(argv[2], O_CREAT | O_RDWR | O_TRUNC, 0644);
        if(fdin == -1 || fdout == -1)
        {
                perror("");
                exit(1);
        }

        aio_cp(fdin, fdout);

        close(fdin);
        close(fdout);

        return 0;
}
